/*!
 * \file WtSelEngine.cpp
 * \project	WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief SEL选股引擎实现文件
 * 
 * 本文件实现了SEL(Selection)选股引擎的核心功能，
 * 包括选股策略管理、任务调度、定时执行、持仓管理等。
 * SEL引擎主要用于股票选股、量化投资组合管理等场景。
 */
#include "WtSelEngine.h"
#include "WtDtMgr.h"
#include "WtSelTicker.h"
#include "TraderAdapter.h"
#include "WtHelper.h"

#include "../WTSTools/WTSLogger.h"
#include "../Share/TimeUtils.hpp"
#include "../Includes/IBaseDataMgr.h"
#include "../Includes/IHotMgr.h"
#include "../Share/StrUtil.hpp"
#include "../Includes/WTSVariant.hpp"
#include "../Includes/WTSSessionInfo.hpp"
#include "../Includes/WTSContractInfo.hpp"
#include "../Share/CodeHelper.hpp"
#include "../Share/decimal.h"

#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
namespace rj = rapidjson;

#include <atomic>

USING_NS_WTP;

/**
 * @brief 生成任务ID
 * 
 * 使用原子操作生成唯一的任务ID
 * 
 * @return 唯一的任务ID
 */
inline uint32_t makeTaskId()
{
	static std::atomic<uint32_t> _auto_task_id{ 1 };
	return _auto_task_id.fetch_add(1);
}

/**
 * @brief 构造函数
 * 
 * 初始化SEL引擎的基本成员变量
 */
WtSelEngine::WtSelEngine()
	: _terminated(false)
	, _cfg(NULL)
{
}

/**
 * @brief 析构函数
 * 
 * 清理资源
 */
WtSelEngine::~WtSelEngine()
{
}

/**
 * @brief 交易时段结束事件处理
 * 
 * 通知事件监听器交易时段结束
 */
void WtSelEngine::on_session_end()
{
	if (_evt_listener)
		_evt_listener->on_session_event(_cur_tdate, false);
}

/**
 * @brief 交易时段开始事件处理
 * 
 * 通知事件监听器交易时段开始，设置引擎为就绪状态
 */
void WtSelEngine::on_session_begin()
{
	if (_evt_listener)
		_evt_listener->on_session_event(_cur_tdate, true);

	_ready = true;
}

/**
 * @brief 初始化事件处理
 * 
 * 通知事件监听器引擎初始化完成
 */
void WtSelEngine::on_init()
{
	if (_evt_listener)
		_evt_listener->on_initialize_event();
}

/**
 * @brief 处理推送的行情数据
 * 
 * 将行情数据传递给实时ticker处理
 * 
 * @param curTick 当前tick数据
 * @param hotFlag 热点标志
 */
void WtSelEngine::handle_push_quote(WTSTickData* curTick, uint32_t hotFlag)
{
	if (_tm_ticker)
		_tm_ticker->on_tick(curTick, hotFlag);
}

/**
 * @brief 处理K线数据
 * 
 * 将K线数据分发给订阅的策略上下文
 * 
 * @param stdCode 标准合约代码
 * @param period 周期
 * @param times 倍数
 * @param newBar 新的K线数据
 */
void WtSelEngine::on_bar(const char* stdCode, const char* period, uint32_t times, WTSBarStruct* newBar)
{
	std::string key = StrUtil::printf("%s-%s-%u", stdCode, period, times);
	const SubList& sids = _bar_sub_map[key];
	for (auto it = sids.begin(); it != sids.end(); it++)
	{
		uint32_t sid = it->first;
		auto cit = _ctx_map.find(sid);
		if (cit != _ctx_map.end())
		{
			SelContextPtr& ctx = (SelContextPtr&)cit->second;
			ctx->on_bar(stdCode, period, times, newBar);
		}
	}

	WTSLogger::info("KBar [%s#%s%d] @ %u closed", stdCode, period, times, period[0] == 'd' ? newBar->date : newBar->time);
}

/**
 * @brief 处理tick数据
 * 
 * 处理实时tick数据，支持复权处理，并分发给订阅的策略上下文和执行器
 * 
 * @param stdCode 标准合约代码
 * @param curTick 当前tick数据
 */
void WtSelEngine::on_tick(const char* stdCode, WTSTickData* curTick)
{
	WtEngine::on_tick(stdCode, curTick);

	_data_mgr->handle_push_quote(stdCode, curTick);

	// 处理实时数据，需要传递给执行器
	{
		_exec_mgr.handle_tick(stdCode, curTick);
	}

	/*
	auto sit = _tick_sub_map.find(stdCode);
	if (sit != _tick_sub_map.end())
	{
		const SubList& sids = sit->second;
		for (auto it = sids.begin(); it != sids.end(); it++)
		{
			uint32_t sid = *it;
			auto cit = _ctx_map.find(sid);
			if (cit != _ctx_map.end() && curTick->volume())
			{
				SelContextPtr& ctx = (SelContextPtr&)cit->second;
				ctx->on_tick(stdCode, curTick);
			}
		}
	}
	*/

	/*
	 *	By Wesley @ 2022.02.07
	 *	这里做了一个重要的调整
	 *	第一个是检查订阅的标记，如果标记为0，不做复权模式，则直接按照原始代码触发ontick
	 *	第二个，如果标记为1，即前复权模式，则将代码转成xxxx-，再次触发ontick
	 *	第三个，如果标记为2，后复权模式，则将代码转成xxxx+，再把tick数据做一个调整，再次触发ontick
	 */
	if(_ready)
	{
		auto sit = _tick_sub_map.find(stdCode);
		if (sit != _tick_sub_map.end())
		{
			const SubList& sids = sit->second;
			for (auto it = sids.begin(); it != sids.end(); it++)
			{
				uint32_t sid = it->first;

				auto cit = _ctx_map.find(sid);
				if (cit != _ctx_map.end())
				{
					SelContextPtr& ctx = (SelContextPtr&)cit->second;
					uint32_t opt = it->second.second;

					// 根据复权选项处理tick数据
					if (opt == 0)
					{
						// 不复权，直接触发
						ctx->on_tick(stdCode, curTick);
					}
					else
					{
						// 构造复权代码
						std::string wCode = stdCode;
						wCode = fmt::format("{}{}", stdCode, opt == 1 ? SUFFIX_QFQ : SUFFIX_HFQ);
						if (opt == 1)
						{
							// 前复权，直接触发
							ctx->on_tick(wCode.c_str(), curTick);
						}
						else //(opt == 2)
						{
							// 后复权，需要调整价格
							WTSTickData* newTick = WTSTickData::create(curTick->getTickStruct());
							WTSTickStruct& newTS = newTick->getTickStruct();

							// 获取复权因子并调整价格
							double factor = _data_mgr->get_adjusting_factor(stdCode, get_trading_date());
							newTS.open *= factor;
							newTS.high *= factor;
							newTS.low *= factor;
							newTS.price *= factor;

							_price_map[wCode] = newTS.price;

							ctx->on_tick(wCode.c_str(), newTick);
							newTick->release();
						}
					}
				}
			}
		}
	}
}

/**
 * @brief 分钟结束事件处理
 * 
 * 检查并执行定时任务，根据任务的周期类型和执行条件决定是否触发任务
 * 
 * @param curDate 当前日期
 * @param curTime 当前时间
 */
void WtSelEngine::on_minute_end(uint32_t curDate, uint32_t curTime)
{
	// 要先计算下一分钟的时间
	uint32_t nextTime = TimeUtils::getNextMinute(curTime, 1);
	if (nextTime < curTime)
		curDate = TimeUtils::getNextDate(curDate);

	uint32_t weekDay = TimeUtils::getWeekDay(curDate);

	// 遍历所有任务，检查是否需要执行
	for (auto& v : _tasks)
	{
		TaskInfoPtr& tInfo = (TaskInfoPtr&)v.second;
		if (tInfo->_time != nextTime)
			continue;

		uint64_t now = (uint64_t)curDate * 10000 + nextTime;
		if (tInfo->_last_exe_time >= now)
			continue;

		// 检查是否为节假日
		if (_base_data_mgr->isHoliday(tInfo->_trdtpl, curDate, true))
			continue;

		// 获取上一个交易日的信息
		uint32_t preTDate = TimeUtils::getNextDate(_cur_date, -1);
		bool bHasHoliday = false;
		uint32_t days = 1;
		while (_base_data_mgr->isHoliday(tInfo->_trdtpl, preTDate, true))
		{
			bHasHoliday = true;
			preTDate = TimeUtils::getNextDate(preTDate, -1);
			days++;
		}
		uint32_t preWD = TimeUtils::getWeekDay(preTDate);

		WTSSessionInfo* sInfo = get_session_info(tInfo->_session, false);

		// 根据任务周期类型判断是否执行
		bool bIgnore = true;
		switch (tInfo->_period)
		{
		case TPT_Daily:
			bIgnore = false;
			break;
		case TPT_Minute:
			{
				uint32_t minutes = sInfo->timeToMinutes(curTime);	// 先将时间转换成分钟数
				if(minutes != 0 && (minutes%tInfo->_time == 0))		// 分钟数必须能被整除，且不为0，满足触发条件
				{
					bIgnore = false;
				}
			}
			break;
		case TPT_Monthly:
			//if (preTDate % 1000000 < _task->_day && _cur_date % 1000000 >= _task->_day)
			//	fired = true;
			if (_cur_date % 1000000 == tInfo->_day)
				bIgnore = false;
			else if (bHasHoliday)
			{
				// 如果有节假日需要处理顺延
				//说明跨月的开始，而且在大于触发日期，顺延到今天
				if ((preTDate % 10000 / 100 < _cur_date % 10000 / 100) && _cur_date % 1000000 > tInfo->_day)
				{
					bIgnore = false;
				}
				else if (preTDate % 1000000 < tInfo->_day && _cur_date % 1000000 > tInfo->_day)
				{
					// 上一天和今天在同一个月，但小于触发日期，而今天大于触发日期，说明正确的触发日期在节假日，顺延到今天
					bIgnore = false;
				}
			}
			break;
		case TPT_Weekly:
			//if (preWD < _task->_day && weekDay >= _task->_day)
			//	fired = true;
			if (weekDay == tInfo->_day)
				bIgnore = false;
			else if (bHasHoliday)
			{
				// 处理周任务的节假日顺延
				if (days >= 7 && weekDay > tInfo->_day)
				{
					bIgnore = false;
				}
				else if (preWD > weekDay && weekDay > tInfo->_day)
				{
					// 上一个交易日的星期大于今天的星期，说明跨了一周
					bIgnore = false;
				}
				else if (preWD < tInfo->_day && weekDay > tInfo->_day)
				{
					bIgnore = false;
				}
			}
			break;
		case TPT_Yearly:
			if (preTDate % 10000 < tInfo->_day && _cur_date % 10000 >= tInfo->_day)
				bIgnore = false;
			break;
		}

		if (bIgnore)
			continue;

		// 执行任务回调
		SelContextPtr ctx = getContext(tInfo->_id);
		StdThreadPtr thrd(new StdThread([ctx, curDate, curTime, nextTime](){
			if (ctx)
				ctx->on_schedule(curDate, curTime, nextTime);
		}));	

		tInfo->_last_exe_time = now;
	}
}

/**
 * @brief 运行SEL引擎
 * 
 * 创建实时ticker，生成标记文件，开始运行
 * 
 * @param bAsync 是否异步运行，默认为false
 */
void WtSelEngine::run(bool bAsync /*= false*/)
{
	// 创建并初始化实时ticker
	WTSVariant* cfgProd = _cfg->get("product");
	_tm_ticker = new WtSelRtTicker(this);
	_tm_ticker->init(_data_mgr->reader(), cfgProd->getCString("session"));

	// 启动之前，先保存所有的策略标记
	{
		rj::Document root(rj::kObjectType);
		rj::Document::AllocatorType &allocator = root.GetAllocator();

		// 添加策略列表
		rj::Value jStraList(rj::kArrayType);
		for (auto& m : _ctx_map)
		{
			const SelContextPtr& ctx = m.second;
			jStraList.PushBack(rj::Value(ctx->name(), allocator), allocator);
		}

		root.AddMember("marks", jStraList, allocator);

		// 添加交易通道列表
		rj::Value jChnlList(rj::kArrayType);
		for (auto& m : _adapter_mgr->getAdapters())
		{
			const TraderAdapterPtr& adapter = m.second;
			jChnlList.PushBack(rj::Value(adapter->id(), allocator), allocator);
		}

		root.AddMember("channels", jChnlList, allocator);

		root.AddMember("engine", rj::Value("SEL", allocator), allocator);

		// 写入标记文件
		std::string filename = WtHelper::getBaseDir();
		filename += "marker.json";

		rj::StringBuffer sb;
		rj::PrettyWriter<rj::StringBuffer> writer(sb);
		root.Accept(writer);
		StdFile::write_file_content(filename.c_str(), sb.GetString());
	}

	// 启动ticker
	_tm_ticker->run();
}

/**
 * @brief 初始化SEL引擎
 * 
 * @param cfg 配置对象
 * @param bdMgr 基础数据管理器
 * @param dataMgr 数据管理器
 * @param hotMgr 热点管理器
 * @param notifier 事件通知器
 */
void WtSelEngine::init(WTSVariant* cfg, IBaseDataMgr* bdMgr, WtDtMgr* dataMgr, IHotMgr* hotMgr, EventNotifier* notifier /* = NULL */)
{
	WtEngine::init(cfg, bdMgr, dataMgr, hotMgr, notifier);

	_cfg = cfg;
	_cfg->retain();
}

/**
 * @brief 添加选股策略上下文
 * 
 * @param ctx 策略上下文指针
 * @param date 执行日期
 * @param time 执行时间
 * @param period 执行周期
 * @param bStrict 是否严格按时间执行
 * @param trdtpl 交易日模板
 * @param sessionID 交易时段ID
 */
void WtSelEngine::addContext(SelContextPtr ctx, uint32_t date, uint32_t time, TaskPeriodType period, bool bStrict /* = true */, const char* trdtpl /* = "CHINA" */, const char* sessionID/* ="TRADING" */)
{
	if (ctx == NULL)
		return;

	// 检查任务是否已存在
	auto it = _tasks.find(ctx->id());
	if(it != _tasks.end())
	{
		WTSLogger::error("Task registration failed: task id %u already registered", ctx->id());
		return;
	}

	// 创建任务信息
	TaskInfoPtr tInfo(new TaskInfo);
	wt_strcpy(tInfo->_name, ctx->name());
	wt_strcpy(tInfo->_trdtpl, trdtpl);
	wt_strcpy(tInfo->_session, sessionID);
	tInfo->_day = date;
	tInfo->_time = time;
	tInfo->_period = period;
	tInfo->_strict_time = bStrict;
	tInfo->_id = makeTaskId();

	_tasks[ctx->id()] = tInfo;

	uint32_t sid = ctx->id();
	_ctx_map[sid] = ctx;
}

/**
 * @brief 获取策略上下文
 * 
 * @param id 策略ID
 * @return 策略上下文指针，如果不存在则返回空指针
 */
SelContextPtr WtSelEngine::getContext(uint32_t id)
{
	auto it = _ctx_map.find(id);
	if (it == _ctx_map.end())
		return SelContextPtr();

	return it->second;
}

/**
 * @brief 处理持仓变化
 * 
 * 处理策略产生的持仓变化信号，包括主力合约和次主力合约的转换，
 * 风控处理，以及传递给执行管理器
 * 
 * @param stdCode 标准合约代码
 * @param diffQty 持仓变化量
 */
void WtSelEngine::handle_pos_change(const char* stdCode, double diffQty)
{
	// 处理主力合约和次主力合约代码转换
	std::string realCode = stdCode;
	if (CodeHelper::isStdFutHotCode(stdCode))
	{
		CodeHelper::CodeInfo cInfo = CodeHelper::extractStdCode(stdCode);
		std::string code = _hot_mgr->getRawCode(cInfo._exchg, cInfo._product, _cur_tdate);
		realCode = CodeHelper::rawMonthCodeToStdCode(code.c_str(), cInfo._exchg);
	}
	else if (CodeHelper::isStdFut2ndCode(stdCode))
	{
		CodeHelper::CodeInfo cInfo = CodeHelper::extractStdCode(stdCode);
		std::string code = _hot_mgr->getSecondRawCode(cInfo._exchg, cInfo._product, _cur_tdate);
		realCode = CodeHelper::rawMonthCodeToStdCode(code.c_str(), cInfo._exchg);
	}

	// 计算目标持仓
	PosInfo& pItem = _pos_map[realCode];
	double targetPos = pItem._volume + diffQty;

	// 风控处理
	bool bRiskEnabled = false;
	if (!decimal::eq(_risk_volscale, 1.0) && _risk_date == _cur_tdate)
	{
		WTSLogger::log_by_cat("risk", LL_INFO, "Risk scale of Strategy Group is %.2f", _risk_volscale);
		bRiskEnabled = true;
	}
	if (bRiskEnabled && targetPos != 0)
	{
		double symbol = targetPos / abs(targetPos);
		targetPos = decimal::rnd(abs(targetPos)*_risk_volscale)*symbol;
	}

	// 记录信号并保存数据
	append_signal(realCode.c_str(), targetPos, false);
	save_datas();

	// 传递给执行管理器处理
	_exec_mgr.handle_pos_change(realCode.c_str(), targetPos);
}

/**
 * @brief 获取品种信息
 * 
 * @param stdCode 标准合约代码
 * @return 品种信息指针
 */
WTSCommodityInfo* WtSelEngine::get_comm_info(const char* stdCode)
{
	return _base_data_mgr->getCommodity(CodeHelper::stdCodeToStdCommID(stdCode).c_str());
}

/**
 * @brief 获取交易时段信息
 * 
 * @param stdCode 标准合约代码
 * @return 交易时段信息指针
 */
WTSSessionInfo* WtSelEngine::get_sess_info(const char* stdCode)
{
	WTSCommodityInfo* cInfo = _base_data_mgr->getCommodity(CodeHelper::stdCodeToStdCommID(stdCode).c_str());
	if (cInfo == NULL)
		return NULL;

	return _base_data_mgr->getSession(cInfo->getSession());
}

/**
 * @brief 获取实时时间
 * 
 * @return 实时时间戳
 */
uint64_t WtSelEngine::get_real_time()
{
	return TimeUtils::makeTime(_cur_date, _cur_raw_time * 100000 + _cur_secs);
}